<template>
  <el-dialog
    v-model="visible"
    title="裁剪头像"
    width="700px"
    :before-close="handleClose"
    destroy-on-close
  >
    <div class="cropper-container">
      <div class="cropper-canvas-wrapper">
        <canvas
          ref="canvasRef"
          :width="canvasSize"
          :height="canvasSize"
          @mousedown="startDrag"
          @mousemove="onDrag"
          @mouseup="endDrag"
          @mouseleave="endDrag"
          @wheel.prevent="handleWheel"
        ></canvas>
        <div class="cropper-mask"></div>
        <div class="cropper-crop-box" :style="cropBoxStyle">
          <div class="crop-box-line line-h"></div>
          <div class="crop-box-line line-v"></div>
          <div class="crop-box-point point-n" @mousedown.stop="startResize('n', $event)"></div>
          <div class="crop-box-point point-e" @mousedown.stop="startResize('e', $event)"></div>
          <div class="crop-box-point point-s" @mousedown.stop="startResize('s', $event)"></div>
          <div class="crop-box-point point-w" @mousedown.stop="startResize('w', $event)"></div>
          <div class="crop-box-point point-ne" @mousedown.stop="startResize('ne', $event)"></div>
          <div class="crop-box-point point-nw" @mousedown.stop="startResize('nw', $event)"></div>
          <div class="crop-box-point point-se" @mousedown.stop="startResize('se', $event)"></div>
          <div class="crop-box-point point-sw" @mousedown.stop="startResize('sw', $event)"></div>
        </div>
      </div>
      
      <div class="cropper-preview">
        <div class="preview-title">预览</div>
        <div class="preview-box">
          <canvas ref="previewCanvasRef" :width="previewSize" :height="previewSize"></canvas>
        </div>
        <div class="preview-info">
          <el-text size="small">{{ Math.round(cropBox.width) }} x {{ Math.round(cropBox.height) }}</el-text>
        </div>
      </div>
    </div>

    <div class="cropper-controls">
      <el-button-group>
        <el-button @click="zoom(0.1)">
          <el-icon><ZoomIn /></el-icon>
        </el-button>
        <el-button @click="zoom(-0.1)">
          <el-icon><ZoomOut /></el-icon>
        </el-button>
        <el-button @click="rotate(-90)">
          <el-icon><RefreshLeft /></el-icon>
        </el-button>
        <el-button @click="rotate(90)">
          <el-icon><RefreshRight /></el-icon>
        </el-button>
        <el-button @click="reset">
          <el-icon><Refresh /></el-icon>
          重置
        </el-button>
      </el-button-group>

      <div class="zoom-slider">
        <el-text size="small">缩放：</el-text>
        <el-slider
          v-model="scale"
          :min="0.1"
          :max="3"
          :step="0.1"
          @input="updateCanvas"
          style="width: 200px; margin-left: 10px;"
        />
      </div>
    </div>

    <template #footer>
      <el-button @click="handleClose">取消</el-button>
      <el-button type="primary" @click="handleCrop" :loading="loading">
        <el-icon><Check /></el-icon>
        确定裁剪
      </el-button>
    </template>
  </el-dialog>
</template>

<script setup>
import { ref, reactive, watch, nextTick } from 'vue'
import { ZoomIn, ZoomOut, RefreshLeft, RefreshRight, Refresh, Check } from '@element-plus/icons-vue'

const props = defineProps({
  modelValue: {
    type: Boolean,
    default: false
  },
  imageFile: {
    type: File,
    default: null
  },
  aspectRatio: {
    type: Number,
    default: 1 // 1:1 正方形
  }
})

const emit = defineEmits(['update:modelValue', 'cropped'])

// 响应式数据
const visible = ref(props.modelValue)
const loading = ref(false)
const canvasRef = ref(null)
const previewCanvasRef = ref(null)
const canvasSize = 500
const previewSize = 150

// 图片数据
const image = ref(null)
const scale = ref(1)
const rotation = ref(0)
const position = reactive({ x: 0, y: 0 })

// 裁剪框
const cropBox = reactive({
  x: 100,
  y: 100,
  width: 300,
  height: 300
})

// 拖动状态
const dragging = ref(false)
const resizing = ref(false)
const resizeDirection = ref('')
const dragStart = reactive({ x: 0, y: 0 })
const positionStart = reactive({ x: 0, y: 0 })
const cropBoxStart = reactive({ x: 0, y: 0, width: 0, height: 0 })

// 计算裁剪框样式
const cropBoxStyle = ref({
  left: '100px',
  top: '100px',
  width: '300px',
  height: '300px'
})

// 监听
watch(() => props.modelValue, (val) => {
  visible.value = val
  if (val && props.imageFile) {
    loadImage()
  }
})

watch(visible, (val) => {
  emit('update:modelValue', val)
})

watch(cropBox, () => {
  updateCropBoxStyle()
  updatePreview()
}, { deep: true })

watch([scale, rotation, position], () => {
  updateCanvas()
  updatePreview()
}, { deep: true })

// 方法
const loadImage = () => {
  const reader = new FileReader()
  reader.onload = (e) => {
    const img = new Image()
    img.onload = () => {
      image.value = img
      reset()
      nextTick(() => {
        updateCanvas()
        updatePreview()
      })
    }
    img.src = e.target.result
  }
  reader.readAsDataURL(props.imageFile)
}

const updateCanvas = () => {
  if (!canvasRef.value || !image.value) return

  const canvas = canvasRef.value
  const ctx = canvas.getContext('2d')
  
  // 清空画布
  ctx.clearRect(0, 0, canvasSize, canvasSize)
  
  // 保存状态
  ctx.save()
  
  // 移动到中心点
  ctx.translate(canvasSize / 2, canvasSize / 2)
  
  // 旋转
  ctx.rotate((rotation.value * Math.PI) / 180)
  
  // 缩放
  ctx.scale(scale.value, scale.value)
  
  // 计算图片显示尺寸
  const imgWidth = image.value.width
  const imgHeight = image.value.height
  const canvasRatio = canvasSize / Math.max(imgWidth, imgHeight)
  const displayWidth = imgWidth * canvasRatio
  const displayHeight = imgHeight * canvasRatio
  
  // 绘制图片（考虑位置偏移）
  ctx.drawImage(
    image.value,
    -displayWidth / 2 + position.x,
    -displayHeight / 2 + position.y,
    displayWidth,
    displayHeight
  )
  
  // 恢复状态
  ctx.restore()
}

const updatePreview = () => {
  if (!previewCanvasRef.value || !canvasRef.value) return

  const sourceCanvas = canvasRef.value
  const previewCanvas = previewCanvasRef.value
  const ctx = previewCanvas.getContext('2d')
  
  // 清空预览画布
  ctx.clearRect(0, 0, previewSize, previewSize)
  
  // 从源画布裁剪区域复制到预览画布
  ctx.drawImage(
    sourceCanvas,
    cropBox.x, cropBox.y, cropBox.width, cropBox.height,
    0, 0, previewSize, previewSize
  )
}

const updateCropBoxStyle = () => {
  cropBoxStyle.value = {
    left: cropBox.x + 'px',
    top: cropBox.y + 'px',
    width: cropBox.width + 'px',
    height: cropBox.height + 'px'
  }
}

const startDrag = (e) => {
  const rect = canvasRef.value.getBoundingClientRect()
  const x = e.clientX - rect.left
  const y = e.clientY - rect.top
  
  // 检查是否点击在裁剪框内
  if (
    x >= cropBox.x &&
    x <= cropBox.x + cropBox.width &&
    y >= cropBox.y &&
    y <= cropBox.y + cropBox.height
  ) {
    return // 在裁剪框内，不拖动图片
  }
  
  dragging.value = true
  dragStart.x = e.clientX
  dragStart.y = e.clientY
  positionStart.x = position.x
  positionStart.y = position.y
}

const onDrag = (e) => {
  if (dragging.value) {
    const dx = e.clientX - dragStart.x
    const dy = e.clientY - dragStart.y
    position.x = positionStart.x + dx / scale.value
    position.y = positionStart.y + dy / scale.value
  } else if (resizing.value) {
    handleResize(e)
  }
}

const endDrag = () => {
  dragging.value = false
  resizing.value = false
}

const startResize = (direction, e) => {
  e.stopPropagation()
  resizing.value = true
  resizeDirection.value = direction
  dragStart.x = e.clientX
  dragStart.y = e.clientY
  Object.assign(cropBoxStart, cropBox)
}

const handleResize = (e) => {
  const dx = e.clientX - dragStart.x
  const dy = e.clientY - dragStart.y
  const dir = resizeDirection.value
  
  let newX = cropBoxStart.x
  let newY = cropBoxStart.y
  let newWidth = cropBoxStart.width
  let newHeight = cropBoxStart.height
  
  if (dir.includes('e')) {
    newWidth = Math.max(50, cropBoxStart.width + dx)
  }
  if (dir.includes('w')) {
    newWidth = Math.max(50, cropBoxStart.width - dx)
    newX = cropBoxStart.x + (cropBoxStart.width - newWidth)
  }
  if (dir.includes('s')) {
    newHeight = Math.max(50, cropBoxStart.height + dy)
  }
  if (dir.includes('n')) {
    newHeight = Math.max(50, cropBoxStart.height - dy)
    newY = cropBoxStart.y + (cropBoxStart.height - newHeight)
  }
  
  // 保持宽高比
  if (props.aspectRatio) {
    if (dir.includes('e') || dir.includes('w')) {
      newHeight = newWidth / props.aspectRatio
    } else {
      newWidth = newHeight * props.aspectRatio
    }
  }
  
  // 限制在画布范围内
  newX = Math.max(0, Math.min(newX, canvasSize - newWidth))
  newY = Math.max(0, Math.min(newY, canvasSize - newHeight))
  newWidth = Math.min(newWidth, canvasSize - newX)
  newHeight = Math.min(newHeight, canvasSize - newY)
  
  cropBox.x = newX
  cropBox.y = newY
  cropBox.width = newWidth
  cropBox.height = newHeight
}

const handleWheel = (e) => {
  const delta = e.deltaY > 0 ? -0.1 : 0.1
  zoom(delta)
}

const zoom = (delta) => {
  scale.value = Math.max(0.1, Math.min(3, scale.value + delta))
}

const rotate = (deg) => {
  rotation.value = (rotation.value + deg) % 360
}

const reset = () => {
  scale.value = 1
  rotation.value = 0
  position.x = 0
  position.y = 0
  
  // 重置裁剪框到中心
  const size = Math.min(canvasSize * 0.6, 300)
  cropBox.x = (canvasSize - size) / 2
  cropBox.y = (canvasSize - size) / 2
  cropBox.width = size
  cropBox.height = size / props.aspectRatio
}

const handleCrop = async () => {
  try {
    loading.value = true
    
    // 创建临时画布用于裁剪
    const tempCanvas = document.createElement('canvas')
    const outputSize = 300 // 输出尺寸
    tempCanvas.width = outputSize
    tempCanvas.height = outputSize
    const ctx = tempCanvas.getContext('2d')
    
    // 从源画布复制裁剪区域
    ctx.drawImage(
      canvasRef.value,
      cropBox.x, cropBox.y, cropBox.width, cropBox.height,
      0, 0, outputSize, outputSize
    )
    
    // 转换为Blob
    tempCanvas.toBlob((blob) => {
      const file = new File([blob], 'avatar.jpg', { type: 'image/jpeg' })
      const url = URL.createObjectURL(blob)
      
      emit('cropped', { file, url })
      handleClose()
      loading.value = false
    }, 'image/jpeg', 0.9)
  } catch (error) {
    console.error('裁剪失败:', error)
    loading.value = false
  }
}

const handleClose = () => {
  visible.value = false
}
</script>

<style scoped>
.cropper-container {
  display: flex;
  gap: 20px;
  justify-content: center;
  align-items: flex-start;
}

.cropper-canvas-wrapper {
  position: relative;
  width: 500px;
  height: 500px;
  background-color: #f5f5f5;
  border: 1px solid #dcdfe6;
  border-radius: 4px;
  overflow: hidden;
}

canvas {
  display: block;
  cursor: move;
}

.cropper-mask {
  position: absolute;
  top: 0;
  left: 0;
  right: 0;
  bottom: 0;
  background-color: rgba(0, 0, 0, 0.5);
  pointer-events: none;
}

.cropper-crop-box {
  position: absolute;
  border: 2px solid #409EFF;
  box-shadow: 0 0 0 9999px rgba(0, 0, 0, 0.5);
  cursor: move;
}

.crop-box-line {
  position: absolute;
  background-color: #409EFF;
  opacity: 0.3;
}

.line-h {
  top: 33.33%;
  left: 0;
  right: 0;
  height: 33.33%;
  width: 100%;
}

.line-v {
  left: 33.33%;
  top: 0;
  bottom: 0;
  width: 33.33%;
  height: 100%;
}

.crop-box-point {
  position: absolute;
  width: 10px;
  height: 10px;
  background-color: #409EFF;
  border: 1px solid #fff;
  border-radius: 50%;
  opacity: 0.8;
}

.point-n {
  top: -5px;
  left: 50%;
  margin-left: -5px;
  cursor: n-resize;
}

.point-e {
  right: -5px;
  top: 50%;
  margin-top: -5px;
  cursor: e-resize;
}

.point-s {
  bottom: -5px;
  left: 50%;
  margin-left: -5px;
  cursor: s-resize;
}

.point-w {
  left: -5px;
  top: 50%;
  margin-top: -5px;
  cursor: w-resize;
}

.point-ne {
  top: -5px;
  right: -5px;
  cursor: ne-resize;
}

.point-nw {
  top: -5px;
  left: -5px;
  cursor: nw-resize;
}

.point-se {
  bottom: -5px;
  right: -5px;
  cursor: se-resize;
}

.point-sw {
  bottom: -5px;
  left: -5px;
  cursor: sw-resize;
}

.cropper-preview {
  display: flex;
  flex-direction: column;
  align-items: center;
}

.preview-title {
  font-size: 14px;
  font-weight: 500;
  margin-bottom: 10px;
  color: #303133;
}

.preview-box {
  width: 150px;
  height: 150px;
  border: 1px solid #dcdfe6;
  border-radius: 50%;
  overflow: hidden;
  background-color: #f5f5f5;
}

.preview-box canvas {
  width: 100%;
  height: 100%;
  border-radius: 50%;
}

.preview-info {
  margin-top: 10px;
  text-align: center;
  color: #909399;
}

.cropper-controls {
  display: flex;
  justify-content: space-between;
  align-items: center;
  margin-top: 20px;
  padding-top: 20px;
  border-top: 1px solid #dcdfe6;
}

.zoom-slider {
  display: flex;
  align-items: center;
}
</style>

